Skip to content

fix: support Python 3.14 compatibility#6419

Open
wolone wants to merge 4 commits into
frappe:developfrom
wolone:develop
Open

fix: support Python 3.14 compatibility#6419
wolone wants to merge 4 commits into
frappe:developfrom
wolone:develop

Conversation

@wolone
Copy link
Copy Markdown

@wolone wolone commented May 12, 2026

No description provided.

@greptile-apps
Copy link
Copy Markdown
Contributor

greptile-apps Bot commented May 12, 2026

Greptile Summary

This PR updates the press app for Python 3.14 compatibility by bumping major dependencies (ansible, python-telegram-bot v13→v22, stripe, playwright), replacing the deprecated dockerfile package with a custom shlex-based parser, migrating the Telegram bot to the async PTB v22 API, and converting functional TypedDict(...) declarations to the class-based form.

  • hooks.py / account.py: The module-level get_frappe_io_auth_url() call in website_redirects is replaced by a new frappe_io_login whitelisted endpoint, fixing a Python 3.14 import-time side-effect problem cleanly.
  • docker_output_parsers.py: The dockerfile library is removed in favor of _get_run_command using shlex.split/shlex.join; however, shlex.join re-encodes tokens (consuming backslash-newline continuations and changing quote style), which breaks the downstream stage-marker splitting and line-fold normalization that get_command relies on — three of the four new tests are expected to fail.
  • telegram_utils.py: The synchronous PTB v13 bot is migrated to async PTB v22 using asyncio.run(); _get_bot_username() now makes a live network call on every respond() invocation instead of using a cached value.

Confidence Score: 3/5

The hooks.py and TypedDict refactors are safe, but the Dockerfile command parser and the Telegram bot changes both introduce correctness issues that would affect production behavior.

The _get_run_command re-encoding via shlex.join silently produces wrong output for three of the four newly-added test cases, meaning deploy-candidate step names displayed in the UI would be garbled or include stray quote characters. The Telegram bot now performs a live network round-trip on every incoming message just to read the bot username, which is an unnecessary regression.

press/press/doctype/deploy_candidate/docker_output_parsers.py and its new test file need the closest attention; press/telegram_utils.py also needs a second look for the username-caching regression.

Important Files Changed

Filename Overview
press/press/doctype/deploy_candidate/docker_output_parsers.py Replaces dockerfile library with a custom _get_run_command using shlex.split/shlex.join; the re-encoding via shlex.join breaks downstream stage-marker stripping and line-fold normalization, causing multiple new tests to fail.
press/telegram_utils.py Migrates from python-telegram-bot v13 (synchronous) to v22 (async) using asyncio.run(); the bot-username lookup is now a live API call on every respond() invocation rather than a cached value.
press/press/doctype/deploy_candidate/test_docker_output_parsers.py New unit-test file for get_command; four test cases are added, but three of them are expected to fail due to the shlex.join re-encoding bug in the implementation.
press/api/account.py Adds frappe_io_login whitelisted endpoint to serve the OAuth redirect dynamically, replacing the module-level call to get_frappe_io_auth_url() at import time.
press/hooks.py Removes the import-time call to get_frappe_io_auth_url() in website_redirects, pointing instead to the new frappe_io_login API method — fixes the Python 3.14 module-level import issue.
pyproject.toml Major dependency bumps for Python 3.14 compatibility: ansible 3→13, python-telegram-bot 13→22, stripe 2→15, playwright 1.49→1.59; removes dockerfile package and adds greenlet.
Prompt To Fix All With AI
Fix the following 2 code review issues. Work through them one at a time, proposing concise fixes.

---

### Issue 1 of 2
press/press/doctype/deploy_candidate/docker_output_parsers.py:228-246
**`shlex.join` re-encodes the command, breaking downstream splitting**

`_get_run_command` calls `shlex.split` (which strips quotes and consumes backslash-newline continuations) and then `shlex.join` (which re-quotes tokens with single quotes). This means: (1) `` `#stage-xxx` `` markers get re-quoted as `'`#stage-xxx`'`, so `get_command`'s `split("`#stage-")` no longer matches them correctly; (2) backslash-newline line folds are consumed by shlex, so `get_command`'s ` \\\n` split no longer works; (3) `"hello world"` (double-quoted) becomes `'hello world'` (single-quoted) in the output.

Tests 1, 2, and 4 (`test_get_command_strips_run_flags_and_stage_marker`, `test_get_command_normalizes_line_folds`, `test_get_command_preserves_quoted_arguments`) will fail for these reasons. The fix is to avoid re-encoding: locate the index in the original `value` string where non-flag content begins, and slice `value` directly rather than reconstructing via `shlex.join`.

### Issue 2 of 2
press/telegram_utils.py:74-80
**`_get_bot_username()` makes a live API call on every `respond()` invocation**

`respond()` now calls `_get_bot_username()` for every incoming Telegram message, which creates a fresh `Bot` instance, calls `asyncio.run()`, and performs a full HTTP round-trip to Telegram just to obtain the bot's username — a value that never changes at runtime. In the old v13 API this was effectively free. A simple fix is to cache the username as an instance attribute on first use (e.g., `self._username`) so only the first message incurs the network call.

Reviews (1): Last reviewed commit: "Merge branch 'develop' into develop" | Re-trigger Greptile

Comment on lines +228 to +246
def _get_run_command(line: str) -> str:
instruction, _, value = line.partition(" ")
if instruction.upper() != "RUN":
return value

value = value.strip()
if value.startswith("["):
return value

try:
parts = shlex.split(value, posix=True)
except ValueError:
return value

for i, part in enumerate(parts):
if not part.startswith("--"):
return shlex.join(parts[i:])

return ""
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P1 shlex.join re-encodes the command, breaking downstream splitting

_get_run_command calls shlex.split (which strips quotes and consumes backslash-newline continuations) and then shlex.join (which re-quotes tokens with single quotes). This means: (1) `#stage-xxx` markers get re-quoted as '#stage-xxx', so get_command's split("#stage-")no longer matches them correctly; (2) backslash-newline line folds are consumed by shlex, soget_command's \\nsplit no longer works; (3)"hello world"(double-quoted) becomes'hello world'` (single-quoted) in the output.

Tests 1, 2, and 4 (test_get_command_strips_run_flags_and_stage_marker, test_get_command_normalizes_line_folds, test_get_command_preserves_quoted_arguments) will fail for these reasons. The fix is to avoid re-encoding: locate the index in the original value string where non-flag content begins, and slice value directly rather than reconstructing via shlex.join.

Prompt To Fix With AI
This is a comment left during a code review.
Path: press/press/doctype/deploy_candidate/docker_output_parsers.py
Line: 228-246

Comment:
**`shlex.join` re-encodes the command, breaking downstream splitting**

`_get_run_command` calls `shlex.split` (which strips quotes and consumes backslash-newline continuations) and then `shlex.join` (which re-quotes tokens with single quotes). This means: (1) `` `#stage-xxx` `` markers get re-quoted as `'`#stage-xxx`'`, so `get_command`'s `split("`#stage-")` no longer matches them correctly; (2) backslash-newline line folds are consumed by shlex, so `get_command`'s ` \\\n` split no longer works; (3) `"hello world"` (double-quoted) becomes `'hello world'` (single-quoted) in the output.

Tests 1, 2, and 4 (`test_get_command_strips_run_flags_and_stage_marker`, `test_get_command_normalizes_line_folds`, `test_get_command_preserves_quoted_arguments`) will fail for these reasons. The fix is to avoid re-encoding: locate the index in the original `value` string where non-flag content begins, and slice `value` directly rather than reconstructing via `shlex.join`.

How can I resolve this? If you propose a fix, please make it concise.

Comment thread press/telegram_utils.py
Comment on lines +74 to +80
def _get_bot_username(self):
return asyncio.run(self._get_bot_username_async())

async def _get_bot_username_async(self):
bot = self.bot
async with bot:
return bot.username
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P1 _get_bot_username() makes a live API call on every respond() invocation

respond() now calls _get_bot_username() for every incoming Telegram message, which creates a fresh Bot instance, calls asyncio.run(), and performs a full HTTP round-trip to Telegram just to obtain the bot's username — a value that never changes at runtime. In the old v13 API this was effectively free. A simple fix is to cache the username as an instance attribute on first use (e.g., self._username) so only the first message incurs the network call.

Prompt To Fix With AI
This is a comment left during a code review.
Path: press/telegram_utils.py
Line: 74-80

Comment:
**`_get_bot_username()` makes a live API call on every `respond()` invocation**

`respond()` now calls `_get_bot_username()` for every incoming Telegram message, which creates a fresh `Bot` instance, calls `asyncio.run()`, and performs a full HTTP round-trip to Telegram just to obtain the bot's username — a value that never changes at runtime. In the old v13 API this was effectively free. A simple fix is to cache the username as an instance attribute on first use (e.g., `self._username`) so only the first message incurs the network call.

How can I resolve this? If you propose a fix, please make it concise.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant